//-*-C++-*-
/***************************************************************************
 *
 *   Copyright (C) 2024-2025 by Andrew Jameson, Willem van Straten and Will Gauvin
 *   Licensed under the Academic Free License version 2.1
 *
 ***************************************************************************/

// dspsr/Signal/General/dsp/LoadToQuantize.h

#ifndef __dsp_LoadToQuantize_h
#define __dsp_LoadToQuantize_h

#include "dsp/BitSeries.h"
#include "dsp/ChanPolSelect.h"
#include "dsp/GenericVoltageDigitizer.h"
#include "dsp/Rescale.h"
#include "dsp/RescaleScaleOffsetDump.h"
#include "dsp/SingleThread.h"
#include "dsp/Sink.h"

namespace dsp {

  //! Decimates input time series
  /*! Chooses a subset of channels and polarizations, rescales, and requantizes */
  class LoadToQuantize : public SingleThread
  {

  public:

    //! Configuration parameters
    class Config;

    //! Set the configuration parameters
    void set_configuration(Config*);

    //! Default constructor with optional configuration parameters
    LoadToQuantize(Sink<BitSeries>* sink, Config* config = 0);

    //! Set the output weights BitSeries sink
    void set_output_weights (Sink<BitSeries>* sink) { output_weights = sink; }

    //! Return a pointer to the Rescale tranformation
    const Rescale* get_rescale () const { return rescale; }

    //! Create the pipeline
    void construct() override;

    //! Prepare before run
    void prepare() override;

    //! Finish after run
    void finish() override;


    /**
     * @brief add a callback handler for when Rescale has updated the scales and offsets.
     *
     * This method is used to add a callback handler on the Rescale step in the pipeline
     * for when the scales and offsets have been updated.
     *
     * The method needs to match the Rescale's callback signature, which is to take pointer
     * to an instance of Rescale.
     *
     * @param instance the instance of the class on which the method should be called.
     * @param method the method on the class to use in the callback.
     */
    template<class Class, typename Method>
    void add_rescale_callback_handler(Class* instance, Method method) {
      if (rescale) {
        rescale->scales_updated.connect(instance, method);
      }
    };

    /**
     * @brief remove a callback handler for when Rescale has updated the scales and offsets.
     *
     * @param instance the instance of the class on which the method should be called.
     * @param method the method on the class to use in the callback.
     */
    template<class Class, typename Method>
    void remove_rescale_callback_handler(Class* instance, Method method) {
      if (rescale) {
        rescale->scales_updated.disconnect(instance, method);
      }
    };

  private:

    //! If we decide to implement a multi-threaded pipeline ...
    friend class LoadToQuantizeN;

    //! Configuration parameters
    Reference::To<Config> config;

    //! Channel and polarisation selection
    Reference::To<ChanPolSelect> chan_pol_select;

    //! Rescale operation transforms data to 0 mean and unit variance
    Reference::To<Rescale> rescale;

    //! Voltage digitizer that quantises the rescaled time series to the specified signed integer bit depth
    Reference::To<GenericVoltageDigitizer> digitizer;

    //! Quantized bit series
    Reference::To<BitSeries> quantized;

    //! Weights associated with the data in the quantized bit series
    Reference::To<BitSeries> quantized_weights;

    //! Output of the pipeline, a BitSeries sink.
    Reference::To<Sink<BitSeries>> output;

    //! Parallel output of the weights associated with the data in output
    Reference::To<Sink<BitSeries>> output_weights;

    //! Scales and offset output dump
    Reference::To<RescaleScaleOffsetDump> scale_offset_dump;

    //! Number of output polarisations
    unsigned output_npol = 0;

    //! Number of output channels
    unsigned output_nchan = 0;

    //! Number of output bits per sample
    unsigned output_nbit = 0;
  };

  //! LoadToQuantize configuration
  class LoadToQuantize::Config : public SingleThread::Config
  {
  public:

    //! Default configuration constructor
    Config();

    /**
     * @brief Stores configuration information shared across the LoadToQuantize pipeline
     *
     * @param _channel_range required output range of channels. Value assigned to channel_range
     * @param _pol_range required output range of polarizations. Value assigned to pol_range
     * @param _output_nbit required output nbit. Value assigned to output_nbit
     */
    Config(const std::string& _channel_range, const std::string& _pol_range, unsigned _output_nbit);

    //! Add command line options
    void add_options(CommandLine::Menu&) override;

    //! parse a command line string of form a:b into a pair of integers
    std::pair<uint32_t, uint32_t> parse_range(const std::string &range, uint32_t max_range);

    //! Number of bits per sample in output
    unsigned output_nbit = 8;

    //! Output channel range (inclusive) to select with format first:last
    std::string channel_range;

    //! Output polarisation range (inclusive) to select with format first:last
    std::string pol_range;

    //! Use Median/MAD for RFI mitigation
    bool use_median_mad = false;

    //! Scales and offset output file name
    std::string scale_offset_filename;

    //! Indicator of whether to do calculation of rescale scales and offset once or all the time.
    bool rescale_constant = false;
  };

} // namespace dsp

#endif // !defined(__dsp_LoadToQuantize_h)
